package vip.xiaomoli.lock.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import vip.xiaomoli.lock.annotation.DistributedLock;
import vip.xiaomoli.lock.component.LockKeyGenerator;
import vip.xiaomoli.lock.constant.LockType;
import vip.xiaomoli.lock.exception.LockFailureException;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class LockAspect implements ApplicationContextAware {

    @Resource
    private RedissonClient redissonClient;

    private ApplicationContext applicationContext;

    @Around("@annotation(lockAnn)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedLock lockAnn) throws Throwable {
        RLock lock = null;
        try {
            LockKeyGenerator keyGenerator = applicationContext.getBean(lockAnn.keyGenerator());
            String lockKey = keyGenerator.generateKey(joinPoint, lockAnn.bizKeys());
            lock = redissonClient.getLock(lockKey);
            LockType lockType = lockAnn.lockType();
            if (LockType.LOCK.equals(lockType)) {
                lock.lock();
                return joinPoint.proceed();
            }

            boolean result = lock.tryLock(lockAnn.waitSeconds(), TimeUnit.SECONDS);
            if (result) return joinPoint.proceed();
            else throw new LockFailureException(lockKey, lockAnn.lockFailMsg());
        } finally {
            if (null != lock) lock.unlock();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}
